package com.bj58.mis.apm.client.boot.impl;

import com.bj58.mis.apm.client.boot.CollectorManager;
import com.bj58.mis.apm.client.boot.schedule.CollectorScheduableBean;
import com.bj58.mis.apm.client.core.AppNodeContext;
import com.bj58.mis.apm.client.core.util.JsonUtil;
import com.bj58.mis.apm.client.core.util.PropertiesBean;
import com.bj58.mis.apm.collector.Collector;
import com.bj58.mis.apm.collector.SchedulableCollector;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

public class CollectorManagerImpl implements CollectorManager {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    private ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(10);

    /**
     * 采集器实例map
     */
    private Map<String, Collector> collectorBeanMap = new HashMap<>();
    /**
     * 采集器调度配置信息map
     */
    private Map<String, CollectorScheduableBean> collectorScheduableBeanMap = new HashMap<>();

    /**
     * 采集器调度器map
     */
    private Map<String, ScheduledFuture> collectorScheduledFutureMap = new HashMap<>();

    private boolean defaultCollectorEnable;

    public CollectorManagerImpl(){
        init();
    }
    public void init(){
        this.defaultCollectorEnable = AppNodeContext.getBooleanValue("collector.enable", false);
        buildSchedulableCollectors();
        updateSchedlableCollectorsFromServer();
        scheduleAllCollectors();

        long period = AppNodeContext.getLongValue("update.collector.shedule.proid", 60);
        scheduledExecutor.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                try {
                    updateCollectorShedule();
                }catch (Exception e){
                    e.printStackTrace();
                    logger.warn("更新采集器调度配置异常。{}", e.toString());
                }

            }
        }, period, period, TimeUnit.SECONDS);
    }

    public void buildSchedulableCollectors(){
        for (String propName : AppNodeContext.propertyNames()){
            if(propName.startsWith("collector.") && propName.endsWith(".class")){
                String type = propName.substring("collector.".length(), propName.length()-".class".length());

                parseCollector(type);
            }
        }
    }

    private void parseCollector(String type) {
        String classNameKey = "collector." + type + ".class";
        String collectorClassName = AppNodeContext.getValue(classNameKey);
        try {
            Class collectorClass = Class.forName(collectorClassName);
            if(Collector.class.isAssignableFrom(collectorClass)){
                Collector collectorObj = (Collector) collectorClass.newInstance();
                collectorObj.setType(type);
                collectorBeanMap.put(type, collectorObj);

                if(SchedulableCollector.class.isAssignableFrom(collectorClass)){
                    SchedulableCollector schedulableCollectorObj = (SchedulableCollector) collectorObj;
                    CollectorScheduableBean csbean = new CollectorScheduableBean();
                    csbean.setEnable(AppNodeContext.getBooleanValue("collector." + type + ".enable", defaultCollectorEnable));
                    csbean.setType(type);
                    csbean.setCollector(schedulableCollectorObj);

                    Long period = AppNodeContext.getLongValue("collector." + type + ".period");
                    if(period == null){
                        logger.warn("collector.{}.period is required!",type);
                        return;
                    }
                    csbean.setPeriod(period);

                    collectorScheduableBeanMap.put(type, csbean);

                }
            }
        } catch (ClassNotFoundException e) {
            logger.warn("collector class: {} is not found!",collectorClassName);
        } catch (IllegalAccessException e) {
            logger.warn("collector class: {} 创建对象失败!", e.toString());
        } catch (InstantiationException e) {
            logger.warn("collector class: {} 创建对象失败!", e.toString());
        }
    }
    private void updateSchedlableCollectorsFromServer() {
        try{
            Map<String, String> collectorPropsFromServer = fetchCollectorPropsFromServer();

            for(Map.Entry<String, CollectorScheduableBean> entry : collectorScheduableBeanMap.entrySet()){
                String type = entry.getKey();
                CollectorScheduableBean csbean = entry.getValue();

                String isEnableFromServer = collectorPropsFromServer.get("collector."+type+".enable");
                if(isEnableFromServer==null){
                    continue;
                }
                String periodFromServer = collectorPropsFromServer.get("collector."+type+".period");
                if(periodFromServer==null){
                    continue;
                }
                boolean isEnable = "true".equalsIgnoreCase(isEnableFromServer);
                long period = Long.parseLong(periodFromServer);

                if(csbean.isEnable() != isEnable || csbean.getPeriod() != period){
                    logger.info("===采集器调度信息变更，type: {}, 原设置为: {}, 新设置为: enable={}, period={}", type, csbean.toString(), isEnable, period);
                    csbean.setEnable(isEnable);
                    csbean.setPeriod(period);

                    AppNodeContext.setValue("collector."+type+".enable", String.valueOf(isEnable));
                    AppNodeContext.setValue("collector."+type+".period", String.valueOf(period));
                }
            }

            for(Map.Entry<String, Collector> entry : collectorBeanMap.entrySet()){
                if(!(entry.getValue() instanceof SchedulableCollector)){
                    String type = entry.getKey();
                    String isEnableFromServer = collectorPropsFromServer.get("collector."+type+".enable");
                    if(isEnableFromServer==null){
                        continue;
                    }else if(!"true".equalsIgnoreCase(isEnableFromServer) && !"false".equalsIgnoreCase(isEnableFromServer)){
                        continue;
                    }
                    boolean isEnable = "true".equalsIgnoreCase(isEnableFromServer);

                    boolean defaultEnable = AppNodeContext.getBooleanValue("collector.enable", false);
                    boolean oldEnable = AppNodeContext.getBooleanValue("collector."+type+".enable", defaultEnable);
                    if(oldEnable != isEnable){
                        logger.info("===采集器调度信息变更，type: {}, 原设置为: enable={}, 新设置为: enable={}", type, oldEnable, isEnable);
                        AppNodeContext.setValue("collector."+type+".enable", String.valueOf(isEnable));
                    }
                }
            }
        }catch (Exception e){
            logger.info(e.toString());
        }

    }



    public void scheduleAllCollectors(){
        logger.info("启动所有SchedulableCollector：");
        for(String type : collectorScheduableBeanMap.keySet()){
            CollectorScheduableBean csbean = collectorScheduableBeanMap.get(type);
            if(csbean.isEnable()){
                logger.info("\t++启动SchedulableCollector, type={}, class={}, period={}s", csbean.getType(), csbean.getCollector().getClass(), csbean.getPeriod());
                sheduleCollector(csbean);
            }else{
                logger.info("\t--未启动SchedulableCollector, type={}, class={}, enable={}", csbean.getType(), csbean.getCollector().getClass(), csbean.isEnable());
            }
        }
    }


    private void sheduleCollector(final CollectorScheduableBean csbean){
        ScheduledFuture future = scheduledExecutor.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                try {
                    logger.debug("====调度采集器: " + csbean.toString());
                    csbean.getCollector().scheduleHandle();
                }catch (Exception e){
                    e.printStackTrace();
                    logger.warn("采集器执行异常。{}", e.toString());
                }

            }
        }, 1, csbean.getPeriod(), TimeUnit.SECONDS);
        collectorScheduledFutureMap.put(csbean.getType(), future);
    }
    private void checkServerProps(Map<String, String> propValueMap) {
        boolean shouldPutPropsToServer = false;
        for(Map.Entry<String, CollectorScheduableBean> entry : collectorScheduableBeanMap.entrySet()){
            String type = entry.getKey();
            String isEnableFromServer = propValueMap.get("collector."+type+".enable");
            if(isEnableFromServer==null){
                shouldPutPropsToServer = true;
                break;
            }else if(!"true".equalsIgnoreCase(isEnableFromServer) && !"false".equalsIgnoreCase(isEnableFromServer)){
                shouldPutPropsToServer = true;
                break;
            }
            String periodFromServer = propValueMap.get("collector."+type+".period");
            if(periodFromServer==null){
                shouldPutPropsToServer = true;
                break;
            }else{
                try{
                    Long.parseLong(periodFromServer);
                }catch (NumberFormatException e){
                    shouldPutPropsToServer = true;
                    break;
                }
            }
        }
        for(Map.Entry<String, Collector> entry : collectorBeanMap.entrySet()){
            if(!(entry.getValue() instanceof SchedulableCollector)){
                String type = entry.getKey();
                String isEnableFromServer = propValueMap.get("collector."+type+".enable");
                if(isEnableFromServer==null){
                    shouldPutPropsToServer = true;
                    break;
                }else if(!"true".equalsIgnoreCase(isEnableFromServer) && !"false".equalsIgnoreCase(isEnableFromServer)){
                    shouldPutPropsToServer = true;
                    break;
                }
            }
        }
        if(shouldPutPropsToServer){
            putCollectorPropsToServer();
        }
    }

    public Map<String, String> fetchCollectorPropsFromServer(){
        String url = getUrl("/properties/get?appName="+AppNodeContext.appName+"&hostName="+AppNodeContext.hostName);
        String resultJson = httpGet(url);
        logger.debug("fetchCollectorPropsFromServer: {}", resultJson);
        //[{"id":1,"appName":"kylindata","hostName":"10.48.89.86:9999","property":"collector.kylinlog.enable","defaultValue":"false","setValue":"true","modified":"Jul 26, 2018 11:37:22 AM"},{"id":2,"appName":"kylindata","hostName":"10.48.89.86:9999","property":"collector.kylinlog.period","defaultValue":"60","setValue":"60","modified":"Jul 26, 2018 11:37:22 AM"},{"id":3,"appName":"kylindata","hostName":"10.48.89.86:9999","property":"collector.kylindata.enable","defaultValue":"false","setValue":"true","modified":"Jul 26, 2018 11:37:22 AM"},{"id":4,"appName":"kylindata","hostName":"10.48.89.86:9999","property":"collector.kylindata.period","defaultValue":"60","setValue":"60","modified":"Jul 26, 2018 11:37:22 AM"},{"id":5,"appName":"kylindata","hostName":"10.48.89.86:9999","property":"collector.httpStatistic.enable","defaultValue":"false","setValue":"false","modified":"Jul 26, 2018 11:37:22 AM"},{"id":6,"appName":"kylindata","hostName":"10.48.89.86:9999","property":"collector.httpStatistic.period","defaultValue":"10","setValue":"10","modified":"Jul 26, 2018 11:37:22 AM"},{"id":7,"appName":"kylindata","hostName":"10.48.89.86:9999","property":"collector.simple.enable","defaultValue":"false","setValue":"false","modified":"Jul 26, 2018 11:37:22 AM"},{"id":8,"appName":"kylindata","hostName":"10.48.89.86:9999","property":"collector.simple.period","defaultValue":"10","setValue":"10","modified":"Jul 26, 2018 11:37:22 AM"},{"id":9,"appName":"kylindata","hostName":"10.48.89.86:9999","property":"collector.demo.enable","defaultValue":"false","setValue":"false","modified":"Jul 26, 2018 11:37:22 AM"},{"id":10,"appName":"kylindata","hostName":"10.48.89.86:9999","property":"collector.demo.period","defaultValue":"10","setValue":"10","modified":"Jul 26, 2018 11:37:22 AM"},{"id":11,"appName":"kylindata","hostName":"10.48.89.86:9999","property":"collector.host.enable","defaultValue":"false","setValue":"false","modified":"Jul 26, 2018 11:37:22 AM"},{"id":12,"appName":"kylindata","hostName":"10.48.89.86:9999","property":"collector.host.period","defaultValue":"10","setValue":"10","modified":"Jul 26, 2018 11:37:22 AM"},{"id":13,"appName":"kylindata","hostName":"10.48.89.86:9999","property":"collector.jvm.enable","defaultValue":"false","setValue":"false","modified":"Jul 26, 2018 11:37:22 AM"},{"id":14,"appName":"kylindata","hostName":"10.48.89.86:9999","property":"collector.jvm.period","defaultValue":"10","setValue":"10","modified":"Jul 26, 2018 11:37:22 AM"}]
        List<Map<String, Object>> propsList = JsonUtil.fromJsonArray(resultJson);
        Map<String, String> propValueMap = new HashMap<>();
        for(Map<String, Object> item : propsList){
            String property = (String) item.get("property");
            String value = (String) item.get("setValue");
            propValueMap.put(property, value);
        }
        //
        checkServerProps(propValueMap);
        return propValueMap;
    }
    public void putCollectorPropsToServer(){
        logger.info("***********更新采集器属性到服务器");
        List<Map<String, Object>> propsList = new ArrayList<>();
        for(Map.Entry<String, CollectorScheduableBean> entry : collectorScheduableBeanMap.entrySet()){
            Map<String, Object> item = new HashMap<>();
            item.put("appName", AppNodeContext.appName);
            item.put("hostName", AppNodeContext.hostName);
            String type = entry.getKey();
            boolean isEnable = entry.getValue().isEnable();
            long period = entry.getValue().getPeriod();
            item.put("property", "collector."+type+".enable");
            item.put("value", String.valueOf(isEnable));
            propsList.add(item);

            item = new HashMap<>(item);
            item.put("property", "collector."+type+".period");
            item.put("value", String.valueOf(period));
            propsList.add(item);
        }
        for(Map.Entry<String, Collector> entry : collectorBeanMap.entrySet()){
            if(!(entry.getValue() instanceof SchedulableCollector)){
                Map<String, Object> item = new HashMap<>();
                item.put("appName", AppNodeContext.appName);
                item.put("hostName", AppNodeContext.hostName);
                String type = entry.getKey();
                boolean defaultEnable = AppNodeContext.getBooleanValue("collector.enable", false);
                boolean isEnable = AppNodeContext.getBooleanValue("collector."+type+".enable", defaultEnable);
                item.put("property", "collector."+type+".enable");
                item.put("value", String.valueOf(isEnable));
                propsList.add(item);
            }
        }
        String url = getUrl("/properties/post");
        httpPost(url, JsonUtil.toJson(propsList));
    }

    private String getUrl(String path){
        String serverUrl = AppNodeContext.getValue("server.http.url");
        if (serverUrl.endsWith("/")){
            serverUrl = serverUrl.substring(0, serverUrl.length()-1);
        }
        return serverUrl + path;
    }
    private String httpGet(String url) {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet(url);

        try {
            CloseableHttpResponse response = httpClient.execute(httpGet);
            return EntityUtils.toString(response.getEntity(), "UTF-8");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }
    private String httpPost(String url, String body){
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPost httpPost = new HttpPost(url);
        httpPost.addHeader("Content-Type", "application/json;charset=UTF-8");
        httpPost.setEntity(new StringEntity(body, Charset.forName("UTF-8")));

        try {
            CloseableHttpResponse response = httpClient.execute(httpPost);
            return EntityUtils.toString(response.getEntity(), "UTF-8");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * 访问服务器采集器调度信息，更新采集器调度任务
     */
    public void updateCollectorShedule(){
        Map<String, String> collectorPropsFromServer = fetchCollectorPropsFromServer();
        for(Map.Entry<String, CollectorScheduableBean> entry : collectorScheduableBeanMap.entrySet()){
            String type = entry.getKey();
            CollectorScheduableBean csbean = entry.getValue();

            String isEnableFromServer = collectorPropsFromServer.get("collector."+type+".enable");
            if(isEnableFromServer==null){
                continue;
            }else if(!"true".equalsIgnoreCase(isEnableFromServer) && !"false".equalsIgnoreCase(isEnableFromServer)){
                continue;
            }
            String periodFromServer = collectorPropsFromServer.get("collector."+type+".period");
            if(periodFromServer==null){
                continue;
            }else{
                try{
                    Long.parseLong(periodFromServer);
                }catch (NumberFormatException e){
                    continue;
                }
            }
            boolean isEnable = "true".equalsIgnoreCase(isEnableFromServer);
            long period = Long.parseLong(periodFromServer);

            if(csbean.isEnable() != isEnable || csbean.getPeriod() != period){
                AppNodeContext.setValue("collector."+type+".enable", String.valueOf(isEnable));
                AppNodeContext.setValue("collector."+type+".period", String.valueOf(period));

                logger.info("===采集器调度信息变更，type: {}, 原设置为: {}, 新设置为: enable={}, period={}", type, csbean.toString(), isEnable, period);
                //重新调度采集任务
                ScheduledFuture future = collectorScheduledFutureMap.remove(type);
                if(future!=null){
                    logger.info("------停止原采集调度任务，type: {}", type);
                    future.cancel(false);
                }
                csbean.setEnable(isEnable);
                csbean.setPeriod(period);
                if(csbean.isEnable()){
                    logger.info("+++++++开启新的采集器调度，type: {}, {}", type, csbean.toString());
                    sheduleCollector(csbean);
                }
            }
        }

        for(Map.Entry<String, Collector> entry : collectorBeanMap.entrySet()){
            if(!(entry.getValue() instanceof SchedulableCollector)){
                String type = entry.getKey();
                String isEnableFromServer = collectorPropsFromServer.get("collector."+type+".enable");
                if(isEnableFromServer==null){
                    continue;
                }else if(!"true".equalsIgnoreCase(isEnableFromServer) && !"false".equalsIgnoreCase(isEnableFromServer)){
                    continue;
                }
                boolean isEnable = "true".equalsIgnoreCase(isEnableFromServer);

                boolean defaultEnable = AppNodeContext.getBooleanValue("collector.enable", false);
                boolean oldEnable = AppNodeContext.getBooleanValue("collector."+type+".enable", defaultEnable);
                if(oldEnable != isEnable){
                    logger.info("===采集器调度信息变更，type: {}, 原设置为: enable={}, 新设置为: enable={}", type, oldEnable, isEnable);
                    AppNodeContext.setValue("collector."+type+".enable", String.valueOf(isEnable));
                }

            }
        }

    }

    @Override
    public Collector getCollector(String type) {
        return this.collectorBeanMap.get(type);
    }

    public static void main(String[] args){
        System.out.println(String.valueOf(true));
    }
}
